#!/usr/bin/python3
# -*- coding:utf-8 -*-
import re
import time
import traceback

from qturboframe.lib.utils.CustomError.custom_error import CustomError
from qturboframe.lib.utils.by import By
from qturboframe.public_ui.h_ui.form import Form as HuiForm
from qturboframe.lib.report.log import logger
from qturboframe.lib.webdriver.exceptions import SeleniumError
from qturboframe.lib.webdriver.remote.webdriver import WebDriver
from qturboframe.lib.webdriver.remote.webelement import WebElement

from BladeFerm.hui12_common.__common__ import deal_form_data
from BladeFerm.hui12_common.function import get_params, display_wait
from BladeFerm.hui2_app.hui2_lib.check import CheckSingle
from BladeFerm.hui2_app.hui2_lib.input_type import InputType
from BladeFerm.hui2_app.hui2_lib.select import Select
from BladeFerm.hui2_app.hui2_lib.select_table import SelectTable
from BladeFerm.hui2_app.hui2_lib.select_tree import SelectTree


class Form(HuiForm):
    '''
    form表单填入
    '''

    __CHECKBOX_SINGLE_CLASS = r'h-checkbox-wrapper'   # 单选框控件
    __SELECT_TABLE_MULTIPLE_CLASS = r'h-selectTable-multiple'   # 反洗钱5.0新版的多选框

    def __init__(self, element_para, father=None, timeout=5, index=1):
        if isinstance(element_para, dict) and By.TEXT in list(element_para.keys())[0]:
            text = element_para.get(By.TEXT)
            xpath = ".//*[contains(text(),'{}')]//ancestor::form[1]".format(text)
            temps = father.find_elements_by_attribute(By.XPATH, xpath, timeout=timeout)
            displayed_forms = []
            if len(temps)>0:
                for temp in temps:
                    if temp.is_displayed and temp.text!='':
                        displayed_forms.append(temp)
            else:
                message = "没有找到xpath为：{} 的form".format(xpath)
                raise SeleniumError(CustomError.Error_EQ03001000_Cannot_Find_Element, message)
            super().__init__(displayed_forms[index-1], timeout=timeout)
        elif isinstance(element_para, dict):
            if isinstance(father, WebDriver):
                self._driver = father
                self.timeout = timeout
            elif isinstance(father, WebElement):
                self._driver = father._parent
                self.timeout = timeout
            else:
                message = 'father参数类型不正确！'
                raise SeleniumError(CustomError.Error_EQ03008001_Parameter_InCorrect, message)
            element_by, element_value, assist = get_params(element_para)
            elements = self._driver.find_elements_by_attribute(element_by, element_value, assist, timeout=3)
            if elements and len(elements) == 1:
                self._element = elements[0]
                self.locator_by = element_by
                self.locator = element_value
            elif elements and len(elements) > 1:
                self.abs_pos = True
                newElements = []
                for ele in elements:
                    style = ele.get_attribute("style")
                    if re.search(r"display: none", style) or re.search(r"height: 0", style):
                        continue
                    else:
                        try:
                            ele.secure_click(0.3)
                            is_clickable = True
                            ele.click()
                        except Exception as e:
                            is_clickable = False
                        if is_clickable:
                            newElements.append(ele)
                self._element = newElements[0] if len(newElements) > 0 else elements[0]
                self.locator_by = element_by
                self.locator = element_value
            else:
                raise SeleniumError(f'没有找到表单，定位表达式是：{element_para}!')
        else:
            super().__init__(element_para, father, timeout)

    def button_option(self, text='查询'):
        '''
        操作表单中按钮。例如点击 查询、重置、更多、简单 等按钮
        :param text: 查询、重置、更多、简单
        :return:
        '''
        # self.element.click()
        # 查找按钮元素
        button_element = self.element.find_elements_by_attribute(
            By.CSS_SELECTOR, 'button.h-btn', 'text:' + text,
            timeout=1)
        # 如果找到了按钮元素，则点击按钮
        if button_element:
            if button_element[0].is_displayed():
                button_element[0].click()
                logger.info(f'点击按钮：{text} 完成')
                return True
            else:
                logger.warning(f'按钮：{text} 不可见！')
                return False
        # 如果没有找到按钮元素，并且按钮文本为“更多”，则查找更多按钮元素
        elif not button_element and text == '更多':
            button_element_2 = self.element.find_elements_by_attribute(
                By.XPATH, F'//button/span[contains(text(), "更多")]', timeout=1)
            # 如果找到了更多按钮元素，则点击按钮
            if button_element_2:
                button_element_2[0].click()
                logger.info(f'表单填充项已经展开')
            # 如果没有找到更多按钮元素，则输出警告信息
            else:
                logger.warning(f'更多 按钮未找到！')
                return False
        # 如果没有找到按钮元素，并且按钮文本为“简单”，则查找简单按钮元素
        elif not button_element and text == '简单':
            button_element_2 = self.element.find_elements_by_attribute(
                By.XPATH, F'//button/span[contains(text(), "简单")]', timeout=1)
            # 如果找到了简单按钮元素，则点击按钮
            if button_element_2:
                button_element_2[0].click()
                logger.info(f'表单填充项已经收起！')
            # 如果没有找到简单按钮元素，则输出警告信息
            else:
                logger.warning(f'简单 按钮未找到！')
                return False
        # 如果没有找到按钮元素，则输出错误信息
        else:
            SeleniumError(f'{text} 按钮未找到！')

    def __fund_label(self, key, label_seq):
        """
        根据 key 找对应的label
        :param key:
        :return:
        """
        if key.endswith('%'):
            sec_key = key[:-1]
            label_elements = self.element.find_elements_by_attribute(
                'xpath', f'.//label[@class="h-form-item-label" and contains(text(), "{sec_key}")]',
                timeout=self.timeout)
        else:
            label_xpath = f'.//label[@title="{key}"]|.//label[text()="{key}"]'       # 一定要用相对定位，点必不可少！！！ 20250418 血淋淋的教训
            label_elements = self.element.find_elements_by_attribute(By.XPATH, label_xpath, timeout=self.timeout)
        if len(label_elements) == 0:
            raise SeleniumError("没有找到“{}”控件".format(key))
        else:
            label = label_elements[label_seq - 1]
            return label

    # 此功能太少了。建议用 auto_fill_group
    def auto_fill(self, inputdata):
        '''
        hui表单自动填充功能

        :Args:
            inputdata: 输入数据，需要定位的表单抬头和待输入文本
                input_data_key  :  输入格式支持2种。
                    1. 直接写key值；
                    2.key值后指定控件序号，'#'连接。如 'key#2'，表示第2个包含key的控件
                input_data_value:  输入格式支持3种。
                    1. 直接写value值；
                    2.value值后指定检查类型，'#'连接
                        - 'value#R': 表示检查value值输入控件是否只读类型
                        - 'value#D': 表示检查value值输入控件是否存在默认值
                        - 'value#M': 表示检查value值输入控件是否是必填项
                        - 'value#S': 表示多选框组件，value值输入控件进行对下拉列表搜索，常用于模糊匹配
                    3.多选框填写值时，用英文 逗号 分割。例如： '中信银行,上海银行'
                    4.日期填写格式：单个日期: '2022-05企业日志查询-18' 范围日期格式： '2022-05企业日志查询-18 - 2022-05企业日志查询-23' (中间的分割符号是：' - ')
        :Raises:
            SeleniumError: 表单填充或校验出错
        :Example:
         - 例: form.auto_fill({'姓名':'admin','年龄':'25','工号':'asdf1234'})
        '''
        logger.info("表单填写开始")
        try:
            for input_key, input_value in inputdata.items():
                if input_value == '':   # 数值为空字符串，
                    continue
                key, values, flag, label_seq, value_type = deal_form_data(input_key, input_value)
                label = self.__fund_label(key, label_seq)
                self.__fund_div_type(label, input_key, input_value)  # 确定元素控件类型
                self.__fill_data(key, values, flag)  # 填充数据
        except Exception as msg:
            raise SeleniumError(str(msg))
        logger.info("表单填写结束")

    def auto_fill_group(self, input_data):
        '''
        hui表单自动填充功能

        :Args:
         - inputdata: 输入数据，需要定位的表单抬头和待输入文本
            - 场景类型: 客户信息异常
              证件地址长度: [True@inner, <, 20]
              地址长度: [True@inner, <, 30]

        :Raises:
            SeleniumError: 表单填充或校验出错

        :Example:
         - 例: form.auto_fill({'姓名':'admin','年龄':'25','工号':'asdf1234'})
        '''
        logger.info(f"表单填写开始，表单数据：{input_data}")
        retry_flag = False
        try:
            for input_key, input_value in input_data.items():
                if input_value == '':  # 数值为空字符串，
                    continue
                key, values, flag, label_seq, value_type = deal_form_data(input_key, input_value)
                label = self.__fund_label(key, label_seq)
                if value_type == 'STRING':
                    self.__fund_div_type(label, key, values)
                    self.__fill_data(key, values, flag)
                elif value_type == 'LIST':
                    for i, unit in enumerate(values):
                        self.__fund_div_type(label, key, unit, index=i)
                        self.__fill_data(key, unit, flag)
                else:
                    tip = f"参数值类型填写错误！值：{input_value} 的类型为： {type(input_value)}。当前仅支持Str和list！"
                    logger.error(tip)
                    raise SeleniumError(tip)
                label.click()   # 填充完毕后点击一下关闭下拉框
                logger.info(f"表单填写完成！")
        except Exception as msg:
            logger.warning(traceback.format_exc())
            logger.warning(f"表单填写失败:{msg}，开始刷新页面重试！")
            retry_flag = True
        return retry_flag


    def __fund_div_type(self, dirver, input_key, input_value, index=0):
        '''
        # 找<div class="h-form-item-content"> 的下级div，根据class的值判断控件类型！  李吉凯 2022-05企业日志查询-18
        反洗钱系统 与公共的区别在于。下拉框，日期等控件与<div class="h-form-item-content">直接还嵌套了1个div。所以的单独重写，不可复用。
        :param dirver:
        :param input_key:
        :param input_value:
        :return:
        '''
        logger.debug(f'{input_key}:{input_value} 查找控件类型元素！')
        try:
            ele = dirver.find_elements_by_attribute(
                        'xpath', './following-sibling::div[@class=\'h-form-item-content\']/'
                                 'div[not(contains(@style, "display: none;"))]', timeout=3)
            if not ele:
                # checkbox空间用的是 <label> 标签
                ele = dirver.find_elements_by_attribute(
                        'xpath', './following-sibling::div[@class=\'h-form-item-content\']/label', timeout=3)
            # logger.debug(f'First fund_div_type: {ele}')
            # 判断是否是控件类型的div，若不是找下层的div
            if len(ele) > index:
                style_text = ele[index].get_attribute('style')
                if style_text and re.search(r'display:.*flex', style_text):
                    ele = ele[index].find_elements_by_attribute(By.XPATH, './div', timeout=1)
                self._control_div = ele[index]
                class_style_text = self._control_div.get_attribute('class')
                logger.info(f'控件类型为: {class_style_text}')
            else:
                self._control_div = None
                raise SeleniumError(f'{input_key}:{input_value} 未找到控件类型元素！')
        except Exception as e:
            logger.error(e)
        if not self._control_div:
            raise SeleniumError(f'{input_key}:{input_value} 未找到控件类型元素！')


    def __confirm_type(self):
        # 获取控件层div的class属性
        div_class_list = self._control_div.get_attribute('class').split(' ')
        # logger.debug(f'div_class_list: {div_class_list}')
        # 判定控件类型
        if self.__INPUT_TYPE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__INPUT_TYPE_CLASS]
        elif self.__INPUT_NUMBER_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__INPUT_NUMBER_CLASS]
        elif self.__TYPE_FIELD_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__TYPE_FIELD_CLASS]
        elif self.__SELECT_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_SINGLE_CLASS]
        elif self.__SELECT_MULTIPLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_MULTIPLE_CLASS]
        elif self.__SELECT_TREE_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TREE_SINGLE_CLASS]
        elif self.__SELECT_TREE_MULTIPLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TREE_MULTIPLE_CLASS]
        elif self.__SELECT_TABLE_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TABLE_SINGLE_CLASS]
        elif self.__SELECT_TABLE_MULTIPLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__SELECT_TABLE_MULTIPLE_CLASS]
        elif self.__CALENDAR_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__CALENDAR_CLASS]
        elif self.__RADIO_CLASS in div_class_list:
            self._elements = self._control_div.find_elements_by_attribute(
                'tag name', 'input', timeout=self.timeout)
            return [self.__RADIO_CLASS]
        elif self.__CHECKBOX_SINGLE_CLASS in div_class_list:
            self._elements = self._control_div
            return [self.__CHECKBOX_SINGLE_CLASS]
        elif self.__CHECKBOX_CLASS in div_class_list:
            self._elements = self._control_div.find_elements_by_attribute(
                'tag name', 'input', timeout=self.timeout)
            return [self.__CHECKBOX_CLASS]
        elif self.__GROUP_CLASS in div_class_list:
            templist = list()
            sub_divs = self._control_div.find_elements_by_attribute(
                'xpath', './div/div', timeout=self.timeout)
            for i in range(0, len(sub_divs)):
                self._control_div = sub_divs[i]
                templist.extend(self.__confirm_type())
            return templist
        else:
            logger.error(f'非标表单控件:{div_class_list}，你确认是否包含测试组件？')
            return []

    def __fill_data(self, key, value, flag):
        # 确定类型
        control_type_list = self.__confirm_type()
        if len(control_type_list) > 1:
            tempList = list()
            for i in range(0, len(control_type_list)):
                tempList.append([value[i]])
            value = tempList
        elif len(control_type_list) == 0:
            pass
        else:
            value = [value]
        # 识别控件并执行
        for i, control_type in enumerate(control_type_list):
            # 根据控件类型选择控件操作
            if control_type == self.__INPUT_TYPE_CLASS:
                self.__deal_input_type(key, value[i], flag)
            elif control_type == self.__INPUT_NUMBER_CLASS:
                self.__deal_input_number(key, value[i], flag)
            elif control_type == self.__TYPE_FIELD_CLASS:
                self.__deal_type_field(key, value[i], flag)
            elif control_type == self.__SELECT_SINGLE_CLASS:
                self.__deal_select_single(key, value[i], flag)
            elif control_type == self.__SELECT_MULTIPLE_CLASS:
                self.__deal_select_multiple(key, value[i], flag)
            elif control_type == self.__SELECT_TREE_SINGLE_CLASS:
                self.__deal_select_tree_single(key, value[i], flag)
            elif control_type == self.__SELECT_TREE_MULTIPLE_CLASS:
                self.__deal_select_tree_multiple(key, value[i], flag)
            elif control_type == self.__SELECT_TABLE_SINGLE_CLASS:
                self.__deal_select_table_single(key, value[i], flag)
            elif control_type == self.__SELECT_TABLE_MULTIPLE_CLASS:
                self.__deal_select_table_multiple(key, value[i], flag)
            elif control_type == self.__RADIO_CLASS:
                self.__deal_radio(key, value[i], flag)
            elif control_type == self.__CHECKBOX_SINGLE_CLASS:
                self.__deal_checkbox_single(key, value[i], flag)
            elif control_type == self.__CHECKBOX_CLASS:
                self.__deal_checkbox(key, value[i], flag)
            elif control_type == self.__CALENDAR_CLASS:
                i_icon = self._elements.find_element_by_attribute(
                    'css selector', 'div.h-date-picker-rel i.h-icon', timeout=self.timeout)
                icon_class_list = i_icon.get_attribute('class').split(' ')
                logger.debug(f"icon_class_list : {icon_class_list}")
                if 'h-icon-time' in icon_class_list:
                    self.__deal_timer(key, value[i], flag)
                elif 'h-icon-activity' in icon_class_list:
                    self.__deal_calendar(key, value[i], flag)
                else:
                    self.__deal_calendar(key, value[i], flag)


    def __deal_input_type(self, key, value, flag=''):
        '''
        处理hui文本框

        :Args:
         - key: 控件名称
         - value: 填充值
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 填充或判断值是出错
        '''
        input_type = InputType(self._elements, timeout=self.timeout)
        text = value  #
        if flag == '':
            logger.info(f'文本框填充，{key}:{text}')
            input_type.set_value(text)
            return
        else:
            if 'R' in flag:
                if input_type.is_readonly():
                    logger.info(key + '只读检查通过。')
                else:
                    raise SeleniumError(key + '只读检查未通过。')
            elif 'D' in flag:
                if input_type.is_default(text):
                    logger.info(key + '默认值检查通过。')
                else:
                    raise SeleniumError(key + '默认值检查未通过。')
            elif 'M' in flag:
                if input_type.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    input_type.set_value(text)
                else:
                    raise SeleniumError(key + '必填项校验未通过。')
            else:
                logger.error(f"不支持的标志，暂只支持 R, D, M")

    def __deal_checkbox_single(self, key, value, flag):
        logger.debug(f'self._elements: {self._elements}, {type(self._elements)}')
        check_div = CheckSingle(self._elements, timeout=self.timeout)
        if flag == '':
            check_div.set_status(value)
        else:
            if 'D' in flag:
                if check_div.is_default(value):
                    logger.info(key + '默认值检查通过。')
                else:
                    raise SeleniumError(key + '默认值检查未通过。')
            else:
                logger.error(f"不支持的标志，暂只支持 D ")

    def __deal_select_single(self, key, value, flag=''):
        '''
        处理hui单选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select = Select(self._elements, timeout=self.timeout)
        text = value[0]
        if flag == '':
            logger.info(key + '进行单选下拉框选择。')
            select.set_value_for_single(text, self.timeout)
        else:
            if 'D' in flag:
                if select.is_default_for_single(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            if 'M' in flag:
                if select.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select.set_value_for_single(text, self.timeout)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)

    def __deal_select_multiple(self, key, value, flag=''):
        '''
        处理hui多选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select = Select(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行下拉框多择。')
            select.set_value_for_multiple(value)
        else:
            if 'D' in flag:
                if select.is_default_for_multiple(value):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            elif 'M' in flag:
                if select.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select.set_value_for_multiple(value)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            else:
                logger.error(f"不支持的标志，暂只支持 D, M")

    def __deal_select_table_single(self, key, value, flag=''):
        '''
        处理hui单选下拉表格框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_table = SelectTable(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行下拉框选择。')
            select_table.set_value_for_single(value[0], self.timeout)
        else:
            if 'L' in flag:
                select_table.check_down_list(value)
            elif 'D' in flag:
                if select_table.is_default_for_single(value[0]):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            elif 'M' in flag:
                if select_table.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_table.set_value_for_single(value[0], self.timeout)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            else:
                logger.error(f"不支持的标志，暂只支持 L, D, M")

    def __deal_select_table_multiple(self, key: str, value: list, flag: str=''):
        '''
        处理hui单选下拉表格框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_table = SelectTable(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行复选树形下拉框选择。')
            select_table.set_value_for_multiple(value, self.timeout)
        else:
            if 'D' in flag:
                if select_table.is_default_for_multiple(value):
                    logger.info(key + '默认值检查通过。')
                else:
                    raise SeleniumError(key + '默认值检查未通过。')
            elif 'M' in flag:
                if select_table.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_table.set_value_for_multiple(value, self.timeout)
                else:
                    raise SeleniumError(key + '必填项校验未通过。')
            else:
                logger.error(f"不支持的标志，暂只支持 D, M")

    def __deal_select_tree_multiple(self, key, value, flag=''):
        '''
        处理hui多选下拉框

        :Args:
         - key: 控件名称
         - value: 下拉选项，list类型
         - flag: 操作标记

        :Returns:
            判断失败返回False

        :Raises:
            SeleniumError: 选择或判断值是出错
        '''
        select_tree = SelectTree(self._elements, timeout=self.timeout)
        if flag == '':
            logger.info(key + '进行复选树形下拉框选择。')
            select_tree.set_value_for_multiple(value)
        else:
            if 'D' in flag:
                if select_tree.is_default(','.join(value)):
                    logger.info(key + '默认值检查通过。')
                else:
                    message = key + '默认值检查未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            elif 'M' in flag:
                if select_tree.is_nonnull():
                    logger.info(key + '必填项校验通过。')
                    select_tree.set_value_for_multiple(value)
                else:
                    message = key + '必填项校验未通过。'
                    raise SeleniumError(CustomError.Error_EQ03009000_Form_Check, message)
            else:
                logger.error(f"不支持的标志，暂只支持 D, M")